package com.minigame.card.domain;

import com.minigame.card.enums.Direction;
import lombok.Data;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;

import static com.minigame.card.util.CardUtil.countList;

@Data
public abstract class CardPlayer<T extends Enum<T>> {
    /**
     * 方位
     */
    protected Direction direction;
    /**
     * 手上牌
     */
    protected List<T> up;
    /**
     * 落地牌
     */
    protected List<List<T>> down = new ArrayList<>();
    /**
     * 所有打出的牌
     */
    protected List<T> discardList = new ArrayList<>();

    /**
     * 能否碰
     * @param discard 其他玩家打出的牌
     */
    public boolean canPong(T discard) {
        return up.stream().filter(card -> card == discard).count() >= 2;
    }
    /**
     * 能否杠
     * @param discard 其他玩家打出的牌
     * @param cellCount 不同牌的总数
     * @param getIndex 获取索引
     */
    public boolean canGang(T discard, int cellCount, Function<T, Integer> getIndex) {
        int[] counts = countList(up, cellCount, getIndex);
        return counts[getIndex.apply(discard)] >= 3;
    }
    /**
     * 暗杠的牌列表
     * @param cellCount 不同牌的总数
     * @param getIndex 获取索引
     * @param indexOf 根据索引获取枚举
     */
    public List<T> tryAnGang(int cellCount, Function<T, Integer> getIndex, Function<Integer, T> indexOf) {
        int[] counts = countList(up, cellCount, getIndex);
        List<T> anGangs = new ArrayList<>();
        for (int i = 0; i < counts.length; i++) {
            if (counts[i] == 4) {
                anGangs.add(indexOf.apply(i));
            }
        }
        return anGangs;
    }

    /**
     * 能否杠
     * @param gangs 杠的牌列表
     */
    public boolean canGang(List<T> gangs) {
        return !gangs.isEmpty();
    }

    /**
     * 能否杠
     * @param discard 其他玩家弃牌
     */
    public boolean canGang(T discard) {
        return up.stream().filter(card -> card == discard).count() == 3;
    }

    /**
     * 下个轮到的玩家
     * @param rest 休息的一方
     */
    public abstract Direction anticlockwiseNext(Direction rest);

    /**
     * 是否需要选择
     * @param options 选项
     * @param discard 其他玩家打出的牌
     */
    public abstract boolean requiredSelect(List<List<T>> options, T discard);

    /**
     * 选择
     * @param options 选项
     * @param index 序号
     */
    public List<T> select(List<List<T>> options, int index) {
        int size = options.size();
        if (index < 0) {
            index = size + index;
        }
        if (index >= size) {
            index = size - 1;
        }
        return options.get(index);
    }

    /**
     * 玩家弃牌吃碰杠后手上牌和落地牌交换
     * @param discard 其他玩家打出的牌
     * @param solid 吃碰杠后形成的牌列表
     * @param comparator 比较规则
     */
    public void exchange(T discard, List<T> solid, Comparator<T> comparator) {
        up.add(discard);
        for (T t : solid) {
            up.remove(t);
        }
        up.sort(comparator);
        solid.sort(comparator);
        down.add(solid);
    }

    /**
     * 明暗杠落牌
     * @param card 杠牌
     * @param anGang 是否暗杠
     */
    public void exchange(T card, boolean anGang) {
        exchange(card, anGang, false);
    }

    /**
     * 明暗杠落牌
     * @param card 杠牌
     * @param anGang 是否暗杠
     * @param discard 是否玩家弃牌
     */
    public void exchange(T card, boolean anGang, boolean discard) {
        //暗杠
        if (anGang) {
            down.add(Arrays.asList(card, card, card, card));
            for (int i = 0; i < 4; i++) {
                up.remove(card);
            }
            return;
        }
        //打杠
        if (discard) {
            down.add(Arrays.asList(card, card, card, card));
            for (int i = 0; i < 3; i++) {
                up.remove(card);
            }
            return;
        }
        //明杠
        for (List<T> team : down) {
            boolean match = team.stream().allMatch(c -> c.equals(card));
            if (match && team.size() == 3) {
                team.add(card);
                up.remove(card);
                return;
            }
        }
    }

    /**
     * 打出牌
     * @param index 索引
     */
    public T discard(int index) {
        if (index < 0) {
            index = Math.max(0, up.size() + index);
        }
        if (index >= up.size()) {
            index = up.size() - 1;
        }
        T discard = up.remove(index);
        if (discardList == null) {
            discardList = new ArrayList<>();
        }
        discardList.add(discard);
        return discard;
    }

    /**
     * 摸到牌
     * @param messList 剩余乱序牌
     */
    public T fetch(List<T> messList) {
        T card = messList.remove(0);
        up.add(card);
        return card;
    }
}
